﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using WimaxSimulator.Gui;
using WimaxSimulator.Wimax;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using WimaxSimulator.Properties;
using System.Collections.ObjectModel;
using System.Threading;

namespace WimaxSimulator.GUI
{
    public partial class SimulatorGUI : Form
    {
        private List<StationUI> stations = new List<StationUI>();

        private List<Traffic> traffics = new List<Traffic>();
        private enum StaType
        {
            BS, RS, MS, NONE
        };
        StaType selectedStationType = StaType.NONE;
        StationUI selectedStation = null;
        StationUI trafficSta1 = null;
        StationUI trafficSta2 = null;
        private const int MARGIN = 40;
        Random r = new Random();

        struct Region
        {
            public int startX;
            public int startY;
            public int endX;
            public int endY;
        }

        private Collection<Region> regions = new Collection<Region>();
        private Collection<Region> Regions
        {
            get
            {
                if (regions.Count == 0)
                {
                    int left = (from station in SimulationEngine.stations
                                select station.Location.X).Min() - MARGIN;
                    int right = (from station in SimulationEngine.stations
                                 select station.Location.X).Max() + MARGIN;
                    int up = (from station in SimulationEngine.stations
                              select station.Location.Y).Min() - MARGIN;
                    int down = (from station in SimulationEngine.stations
                                select station.Location.Y).Max() + MARGIN;
                    regions.Add(new Region() { startX = left, startY = up, endX = (left + right) / 2, endY = (up + down) / 2 });
                    regions.Add(new Region() { startX = (left + right) / 2 + 1, startY = up, endX = right, endY = (up + down) / 2 });
                    regions.Add(new Region() { startX = left, startY = (up + down) / 2 + 1, endX = (left + right) / 2, endY = down });
                    regions.Add(new Region() { startX = (left + right) / 2 + 1, startY = (up + down) / 2 + 1, endX = right, endY = down });
                }
                return regions;
            }
        }
        public SimulatorGUI()
        {
            SimulationEngine.SimulationTimeelepasedEvent += new SimulationEngine.SimulationTimeElapsed(engine_SimulationTimeElepasedEvent);
            InitializeComponent();
            StationUI.CoverageEnable = coverageCheckBox.Checked;
            settingsGrid.SelectedObject = Settings.Default;
            trafficBindingSource.DataSource = traffics;
        }



        void engine_SimulationTimeElepasedEvent()
        {
            this.Invoke(new EventHandler(delegate
            {
                simulationTime.Text = SimulationEngine.SimulationTime + "";
                //TODO: draw packet transitions
                if (refreshCheckBox.Checked)
                    this.Refresh();
            }));
        }

        private void topologyPanel_Paint(object sender, PaintEventArgs e)
        {
            foreach (StationUI station in stations)
            {
                station.Paint(e.Graphics);
            }
        }

        [STAThread]
        public static void Main(String[] args)
        {
            try
            {
                new SimulatorGUI().ShowDialog();
            }
            catch (Exception e)
            {
                MessageBox.Show(e.ToString());
            }
        }

        private void startSimulationButton_Click(object sender, EventArgs e)
        {
            SimulationEngine.RelayingMode = relayingModeCheck.Checked;
            startSimulation();
            Refresh();
        }

        private void startSimulation()
        {
            SimulationEngine.McsEnabled = mcsCheckBox.Checked;
            SimulationEngine.SimulationTime = 0;
            SimulationEngine.SimulationDuration = int.Parse(simulationTime.Text);
            this.Invoke(new EventHandler(delegate
                {
                    startSimulationButton.Enabled = false;
                    assignTraffics();
                }));
            SimulationEngine.Clear();
            SimulationEngine.run();
            this.Invoke(new EventHandler(delegate
                { startSimulationButton.Enabled = true; }));
        }

        private void openTopologyButton_Click(object sender, EventArgs e)
        {
            SimulationEngine.stations.Clear();
            openFileDialog.CheckFileExists = true;
            if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                BinaryFormatter bf = new BinaryFormatter();
                Stream stream = openFileDialog.OpenFile();
                stations = (List<StationUI>)bf.Deserialize(stream);
                System.Threading.Thread.Sleep(1000);
                stream.Close();
                foreach (StationUI s in stations)
                    s.Initialize();
                SimulationEngine.NextId = (from s in SimulationEngine.stations select s.Id).Max();
                readTraffics();
                Refresh();
            }
        }

        private void readTraffics()
        {
            traffics = new List<Traffic>();
            foreach (StationUI s in stations)
            {
                traffics.AddRange(s.Station.TrafficDestinations);
            }
            trafficBindingSource.DataSource = traffics;

            trafficGrid.DataSource = trafficBindingSource;
        }

        private void saveTopologyButton_Click(object sender, EventArgs e)
        {
            saveFileDialog.CreatePrompt = true;
            if (saveFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                BinaryFormatter bf = new BinaryFormatter();
                Stream s = saveFileDialog.OpenFile();
                bf.Serialize(s, stations);
                s.Close();
            }
        }

        private void stationButton_Click(object sender, EventArgs e)
        {
            if (sender == bsButton)
                selectedStationType = StaType.BS;
            if (sender == rsButton)
                selectedStationType = StaType.RS;
            if (sender == msButton)
                selectedStationType = StaType.MS;
            enableButtons(false);
        }

        private void enableButtons(bool flag)
        {
            if (flag)
            {
                bsButton.Enabled = true;
                rsButton.Enabled = true;
                msButton.Enabled = true;
            }
            else
            {
                bsButton.Enabled = false;
                rsButton.Enabled = false;
                msButton.Enabled = false;
            }
        }

        private void topologyPanel_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (selectedStationType != StaType.NONE)
                {
                    enableButtons(true);
                    switch (selectedStationType)
                    {
                        case StaType.BS:
                            stations.Add(new BaseStationUI(new BaseStation(SimulationEngine.NextId, SimulationEngine.NextAddressBlock, Settings.Default.UlBw, Settings.Default.DlBw, e.Location, 140),imageList.Images[0]));
                            break;
                        case StaType.RS:
                            stations.Add(new RelayStationUI(new RelayStation(SimulationEngine.NextId, Settings.Default.DlBw, e.Location, 120),imageList.Images[1]));
                            break;
                        case StaType.MS:
                            stations.Add(new MobileStationUI(new MobileStation(SimulationEngine.NextId, e.Location, 100), imageList.Images[2]));
                            break;
                    }
                    Refresh();
                    selectedStationType = StaType.NONE;
                }

            }
            else if (e.Button == MouseButtons.Right)
            {
                if (trafficSta1 == null)
                    trafficSta1 = getStationAtLoc(e.Location);
                else
                {
                    trafficSta2 = getStationAtLoc(e.Location);
                    InputForm input = new InputForm(trafficSta1.Station, trafficSta2.Station);
                    input.Location = e.Location;
                    input.ShowDialog();
                    trafficSta1 = null;
                }


            }
        }

        private void topologyPanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                selectedStation = getStationAtLoc(e.Location);
            }
        }

        private StationUI getStationAtLoc(Point location)
        {
            return (from s in stations
                    where s.Intersects(location)
                    select s).FirstOrDefault<StationUI>();
        }

        private Station getStationById(int id)
        {
            if (id == 0)
                return SimulationEngine.Sink;
            return (from s in stations
                    where s.Station.Id == id
                    select s.Station as MobileStation).SingleOrDefault<MobileStation>();
        }

        private void topologyPanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (selectedStation != null)
                {
                    selectedStation.Station.Location = e.Location;
                    Refresh();
                }
            }
        }

        private void topologyPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && selectedStation != null)
            {
                selectedStation.Station.Location = e.Location;
                Refresh();
            }
        }

        private void assignTraffics()
        {
            SimulationEngine.Sink.TrafficDestinations.Clear();
            foreach (StationUI s in stations)
                s.Station.TrafficDestinations.Clear();
            foreach (Traffic t in traffics)
            {
                Station s = getStationById(t.Source);
                s.TrafficDestinations.Add(t);
            }
        }

        private void saveSettingsButton_Click(object sender, EventArgs e)
        {
            Settings.Default.Save();
            foreach (MobileStation m in SimulationEngine.MobileStations)
                m.UplinkBandwidth = Settings.Default.MsTotalBw;
            foreach (BaseStation b in SimulationEngine.BaseStations)
            {
                b.TotalUlBandwidth = Settings.Default.UlBw;
                b.DownlinkBandwidth = Settings.Default.DlBw;
            }
            foreach (RelayStation r in SimulationEngine.RelayStations)
                r.DownlinkBandwidth = Settings.Default.DlBw;
        }

        private void clearTopButton_Click(object sender, EventArgs e)
        {
            stations.Clear();
            SimulationEngine.NextId = 1;
            traffics.Clear();
            trafficGrid.DataSource = null;
            Refresh();
        }

        private void uniformButton_Click(object sender, EventArgs e)
        {
            try
            {
                foreach (Region reg in Regions)
                    for (int i = 0; i < Settings.Default.NumberOfMSs / 4; i++)
                    {
                        Point newP;
                        while (true)
                        {
                            int x = r.Next(reg.startX, reg.endX);
                            int y = r.Next(reg.startY, reg.endY);
                            newP = new Point(x, y);
                            if (null == (from sta in SimulationEngine.stations where Station.Subtract(sta.Location, newP) < 40 select sta).FirstOrDefault<Station>())
                                break;
                        }
                        stations.Add(new MobileStationUI(new MobileStation(SimulationEngine.NextId, newP, 100),imageList.Images[2]));
                    }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            Refresh();
        }

        private void generateTrafficButton_Click(object sender, EventArgs e)
        {

            generateTraffic();
        }
        private void generateTraffic()
        {
            traffics.Clear();
            traffics = new List<Traffic>();
            foreach (MobileStation m in SimulationEngine.MobileStations)
            {
                bool inRegion = r.NextDouble() < Settings.Default.InRegionTrafficRate;
                MobileStation dest = selectDestination(m.Location, inRegion);
                traffics.Add(new Traffic() { Destination = dest.Id, EndTime = 100, Source = m.Id, StartTime = 0 });
                inRegion = r.NextDouble() < Settings.Default.InRegionTrafficRate;
                dest = selectDestination(m.Location, inRegion);
                traffics.Add(new Traffic() { Destination = dest.Id, EndTime = 100, Source = m.Id, StartTime = 0 });
                //traffics.Add(new Traffic() { Destination = m.Id, EndTime = 100, Source = 0, StartTime = 0 });
            }
            this.Invoke(new EventHandler(delegate { trafficBindingSource.DataSource = traffics; }));
        }
        private bool inRegion(Region r, Point p)
        {
            return r.startY - MARGIN <= p.Y && r.startX - MARGIN <= p.X && r.endX > p.X && r.endY > p.Y;
        }

        private Region getRegion(Point p)
        {
            return (from r in Regions
                    where inRegion(r, p)
                    select r).First<Region>();
        }

        private MobileStation selectDestination(Point point, bool near)
        {
            Region reg = getRegion(point);
            List<MobileStation> results = new List<MobileStation>();
            int i = 0;
            if (near)
                while (results.Count == 0)
                {
                    results = (from s in SimulationEngine.MobileStations
                               where s.Location != point && Station.Subtract(s.Location, point) <= Settings.Default.TrafficRadius + i
                               select s).ToList<MobileStation>();
                    i += 10;
                }
            else
                while (results.Count == 0)
                {
                    results = (from s in SimulationEngine.MobileStations
                               where point != s.Location && !inRegion(reg, s.Location)
                               select s).ToList<MobileStation>();
                    i += 10;
                }
            int index = r.Next(0, results.Count);
            return results[index];
        }

        private void findBestMetricsButton_Click(object sender, EventArgs e)
        {
            try
            {
                if (File.Exists(Settings.Default.SimulationResults))
                    File.Delete(Settings.Default.SimulationResults);


                Console.WriteLine("Shortcut\tInRegion\tUL\tDL\tMcs\tHopCount\tSentData\tRecvData\tLoad\tThreshold\tTrafficBW\tThroughput\tCongestion");

            }
            catch (IOException)
            {
                MessageBox.Show("Please close file: " + Settings.Default.SimulationResults);
            }
            Thread engineThread = new Thread(FindBestMetrics);
            engineThread.Start();
            //engineThread.Join();
        }

        private void FindBestMetrics()
        {
            Settings.Default.ShowDialog = false;

            //double tresup = 1.1, tresdown = 1.1;
            int mcsup = 2, mcsdown = 2, hopdown = 1, hopup = 1, trafdown = 50, trafinterval = 50, loaddown = 1, loadup = 1, recvdown = 0, recvup = 1, sentdown = 0, sentup = 1, totalUlup = 7000, totalULdown = 7000, totalDLup = 8000, totalDldown = 8000;
            int totalIteration = (5 * 2 * 1 * (mcsup + 1 - mcsdown) * (hopup + 1 - hopdown) * (Settings.Default.MsTotalBw - trafdown + trafinterval) / trafinterval * (loadup + 1 - loaddown) * (recvup + 1 - recvdown) * (sentup + 1 - sentdown) * (((totalUlup - totalULdown) / 1000) + 1) * (((totalDLup - totalDldown) / 1000) + 1));

            int i = 0;
            //for (double tres = tresdown; tres <= tresup; tres += 0.1)

            for (int relaying = 0; relaying < 2; relaying++)
            {
                SimulationEngine.RelayingMode = relaying == 0 ? false : true;
             
                for (float inRegion = 0; inRegion <= 1; inRegion += 0.25F)
                {
                    Settings.Default.InRegionTrafficRate = inRegion;
                    generateTraffic();
                    for (int ul = totalULdown; ul <= totalUlup; ul += 1000)
                        for (int dl = totalDldown; dl <= totalDLup; dl += 1000)
                            for (int mcs = mcsdown; mcs <= mcsup; mcs++)
                                for (int hop = hopdown; hop <= hopup; hop++)
                                    for (int traf = trafdown; traf <= Settings.Default.MsTotalBw; traf += trafinterval)
                                        for (int load = loaddown; load <= loadup; load++)
                                            for (int recv = recvdown; recv <= recvup; recv++)
                                                for (int sent = sentdown; sent <= sentup; sent++)
                                                {
                                                    Settings.Default.AccessChangeTreshold = (float)1.1;
                                                    Settings.Default.HopCountCoefficient = hop;
                                                    Settings.Default.LoadCoefficient = load;
                                                    Settings.Default.McsCoefficient = mcs;
                                                    Settings.Default.MsTrafficBandwidth = traf;
                                                    Settings.Default.RecvDataCoefficient = recv;
                                                    Settings.Default.SentDataCoefficient = sent;
                                                    Settings.Default.DlBw = dl;
                                                    Settings.Default.UlBw = ul;
                                                    startSimulation();
                                                    this.Invoke(new EventHandler(delegate
                                                    {
                                                        simulationProgress.Value = ++i * 100 / totalIteration;
                                                        this.Text = Settings.Default.SimulationResults + "\t Simulation Running " + simulationProgress.Value + "";
                                                    }));
                                                }
                }
            }
            Settings.Default.ShowDialog = true;
        }

        private void coverageCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            StationUI.CoverageEnable = coverageCheckBox.Checked;
            Refresh();
        }

        private void addStationButton_Click(object sender, EventArgs e)
        {
            if (selectedStationType != StaType.NONE)
            {
                enableButtons(true);
                switch (selectedStationType)
                {
                    case StaType.BS:
                        stations.Add(new BaseStationUI(new BaseStation(SimulationEngine.NextId, SimulationEngine.NextAddressBlock, Settings.Default.UlBw, Settings.Default.DlBw, new Point(int.Parse(xTextBox.Text), int.Parse(yTextBox.Text)), 140), imageList.Images[0]));
                        break;
                    case StaType.RS:
                        stations.Add(new RelayStationUI(new RelayStation(SimulationEngine.NextId, Settings.Default.DlBw, new Point(int.Parse(xTextBox.Text), int.Parse(yTextBox.Text)), 120), imageList.Images[1]));
                        break;
                    case StaType.MS:
                        stations.Add(new MobileStationUI(new MobileStation(SimulationEngine.NextId, new Point(int.Parse(xTextBox.Text), int.Parse(yTextBox.Text)), 100), imageList.Images[2]));
                        break;
                }
                Refresh();
                selectedStationType = StaType.NONE;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            for (int i = 110; i <= 830; i += 90)
            {
                for (int j = 110; j <= 830; j += 90)
                {
                    if ((i == 200 && (j == 200 || j == 740)) /*|| (i == 470 && j == 470)*/ || (i == 740 && (j == 200 || j == 740)))
                    {
                        stations.Add(new BaseStationUI(new BaseStation(SimulationEngine.NextId, SimulationEngine.NextAddressBlock, Settings.Default.UlBw, Settings.Default.DlBw, new Point(i, j), 140), imageList.Images[0]));
                    }
                    else
                        stations.Add(new RelayStationUI(new RelayStation(SimulationEngine.NextId, Settings.Default.DlBw, new Point(i, j), 120),imageList.Images[1]));
                }
            }
            Refresh();
        }
    }
}
